package com.obixlabs.security.credentialsstore.server.serviceimpl;

import java.rmi.RemoteException;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

import javax.jws.WebService;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.obixlabs.commons.ObixRuntimeException;
import com.obixlabs.commons.util.RandomCharacterGenerator;
import com.obixlabs.security.credentialsstore.model.ObixWebSSOSession;
import com.obixlabs.security.credentialsstore.model.User;
import com.obixlabs.security.credentialsstore.services.SecurityServiceConstants;
import com.obixlabs.security.credentialsstore.services.UserManagementService;
import com.obixlabs.security.credentialsstore.services.WebSSOService;

@WebService(endpointInterface="com.obixlabs.security.credentialsstore.services.WebSSOService")
public class WebSSOServiceImpl implements WebSSOService
{
	
	private static final long DEFAULT_CLEANUP_INTERVAL = 12000;	
	
	private static final Logger logger 
		= LoggerFactory.getLogger(WebSSOServiceImpl.class);
	
	private UserManagementService userManagementService;
	private Map<String, ObixWebSSOSession> savedSessions;
	private Map<String, String> userIdToCookieMap;
	private ReadWriteLock readWriteLock;
	private RandomCharacterGenerator randomCharGenerator;
	
	private WebSSOSessionCleanUpThread cleanUpThread;
	
	private long timeout;
	private long refreshInterval;
	private long cleanupInterval;
	
	
	public WebSSOServiceImpl()
	{
		savedSessions = new HashMap<String, ObixWebSSOSession>();
		userIdToCookieMap = new HashMap<String, String>();
		readWriteLock = new ReentrantReadWriteLock(true);
		randomCharGenerator = new RandomCharacterGenerator();
	}
	
	@Override
	public User getUserDetails(String webSSOCookie)
	{
		User result = null;
		
		lockForRead();
		
		ObixWebSSOSession session;
		try
		{
			session = savedSessions.get(webSSOCookie);
			
			if (session!=null && session.isValid(timeout))
			{
				// if the session is not yet 
				// due for a refresh, then touch it 
				// to indicate that session has been accessed.
				if (!session.isRefreshDue(refreshInterval))
					session.touch();
				
				result = session.getUser();
			}//end if
		}
		finally { unlockAfterRead();} //possibly called twice
		
		//if the session is due for refresh,
		//then refresh it, and if the account is no longer valid,
		//then return null
		if (session!=null && session.isRefreshDue(refreshInterval))
		{
			if (logger.isDebugEnabled())
				logger.debug("Refreshing session for cookie "+ 
							webSSOCookie);
			
			if (!refreshSession(session))
				result = null;					
		}
		
		if (logger.isDebugEnabled())
			logger.debug("Return user-details (" + result + 
						") for cookie " + webSSOCookie);
		
		return result;
	}

	@Override
	public boolean isSessionActive(String webSSOCookie)
	{
		boolean result = false;
		
		lockForRead();
		ObixWebSSOSession session;
		try
		{
			session = savedSessions.get(webSSOCookie);
			
			if (session!=null && session.isValid(timeout))
			{
				session.touch();
				result = true;
			}
		}
		finally {unlockAfterRead();} //possibly called twice

		if (session!=null && !session.isValid(timeout))
			removeSession(webSSOCookie);
	
		if (logger.isDebugEnabled())
			logger.debug("Session for cookie " + webSSOCookie + 
						" is active? " + result);
		
		return result;
	}

	@Override
	public void logout(String webSSOCookie)
	{ removeSession(webSSOCookie); }

	@Override
	public String signon(String username, String password)
	{
		String result;
		
		if (logger.isDebugEnabled())
			logger.debug("Starting signon for user " + username);
		
		ObixWebSSOSession session = createAndSaveSession(username, password);
		if (session!=null)
			result = session.getSessionId();
		else result = null;
		return result;
	}
	
	public void init()
	{
		if (cleanupInterval<=0)
			cleanupInterval = DEFAULT_CLEANUP_INTERVAL;
		
		cleanUpThread = 
			new WebSSOSessionCleanUpThread(this,cleanupInterval);
		
		Thread target = new Thread(cleanUpThread);
		target.setDaemon(true);
		target.start();
	}
	
	public Set<String> getIdsForAllSessions()
	{
		Set<String> result;		
		lockForRead();
		try{
			result = savedSessions.keySet();
		} finally {
			unlockAfterRead();
		}		
		return result;
	}	
	
	
	public long getRefreshInterval(){return refreshInterval;}
	public void setRefreshInterval(long refreshInterval)
	{this.refreshInterval = refreshInterval;}
	
	public long getTimeout(){return timeout;}
	public void setTimeout(long timeout){this.timeout = timeout;}
	
	public long getCleanupInterval(){return cleanupInterval;}
	public void setCleanupInterval(long cleanupInterval)
	{this.cleanupInterval = cleanupInterval;}
	
	

	public UserManagementService getUserManagementService()
	{return userManagementService;}
	public void setUserManagementService(
			UserManagementService userManagementService)
	{this.userManagementService = userManagementService;}
	
	private boolean refreshSession(ObixWebSSOSession session)
	{
		boolean result;
		lockForWrite();		
		try
		{
			User refreshedUserDetails = 
				userManagementService.refreshUserDetails(
						session.getUser());
			if (refreshedUserDetails!=null)
			{				
				session.setUser(refreshedUserDetails);
				result = true;
				if (logger.isDebugEnabled())
					logger.debug("Refreshed session " + session.getSessionId());
			}
			else
			{
				if (logger.isDebugEnabled()) 
					logger.info("User details no longer valid for '" 
							+ session.getUser() + "', removing session "+ 
							session.getSessionId());
				
				removeSession(session.getSessionId());
				result = false;
			}
		}
		catch (RemoteException exce)
		{
			throw new ObixRuntimeException(exce);
		}		
		finally {unlockAfterWrite();}
		
		return result;
	}
	
	private void removeSession(String webSSOCookie)
	{
		lockForWrite();		
		try 
		{ 
			ObixWebSSOSession session = 
				savedSessions.remove(webSSOCookie);
			
			//also remove from userId to cookie map
			if (session!=null)
			{
				userIdToCookieMap.remove(
						session.getUser().getUserId());
				
				if (logger.isDebugEnabled())					
					logger.debug("Removed session " + webSSOCookie);
				
			}
			else
			{
				if (logger.isDebugEnabled())
					logger.debug("Session " + webSSOCookie + 
							" has already been removed.");
			}
			
		} finally {unlockAfterWrite();}
	}
	
	private ObixWebSSOSession 
			createAndSaveSession(String username, 
								 String password)
	{
		ObixWebSSOSession result;
		lockForWrite();		
		try
		{
			result = getExistingSession(username);
			
			if (logger.isDebugEnabled())
				logger.debug("Found an existing session for user " 
							+ username +". Will return that instead.");
			
			if (result==null)
			{
				User user = 
					userManagementService.authenticateHumanUser(username, 
																password);
				if (user!=null)
				{
					String sessionId = generateSessionId(user.getUserId());
					result = new ObixWebSSOSession(user, sessionId);
					
					//save the session to the internal store
					savedSessions.put(result.getSessionId(), result);
					userIdToCookieMap.put(username, result.getSessionId());
					
					if (logger.isDebugEnabled())
						logger.debug("Authenticated user " + username+
									" and created session.");
					
				}
				else
				{						
					result = null;
					if (logger.isDebugEnabled())
						logger.debug("Authentication failed for user " + username);
					
				}
			}
		}
		catch (RemoteException exce)
		{
			throw new ObixRuntimeException(exce);
		}
		finally {unlockAfterWrite();}
		return result;
	}
	
	private ObixWebSSOSession getExistingSession(String username)
	{
		ObixWebSSOSession result;
		lockForWrite();		

		try
		{
			if (userIdToCookieMap.containsKey(username))
			{
				String webSSOCookie = userIdToCookieMap.get(username);
				if (isSessionActive(webSSOCookie))
					result = savedSessions.get(webSSOCookie);
				else result= null;
			}
			else result = null;
		}
		finally { unlockAfterWrite();}
		
		return result;
	}//end method def
	
	
	private String generateSessionId(String userId)
	{
		StringBuffer result = new StringBuffer();
		
		int paddingSize = SecurityServiceConstants.WEB_SSO_COOKIE_LENGTH - userId.length();
		int lpad = paddingSize/2;
		int rpad = paddingSize-(lpad+userId.length());
		
		for (int i = 0;i<lpad;i++)
			result.append(randomCharGenerator.
							nextAlphaNumericChar());
		
		//append the userId, but replace . with nothing
		result.append(userId.replace(".", ""));
		
		for (int i = 0;i<rpad;i++)
			result.append(randomCharGenerator.
							nextAlphaNumericChar());
				
		return result.toString();		
	}
	
	private void lockForRead() {readWriteLock.readLock().lock();}
	private void unlockAfterRead()
	{readWriteLock.readLock().unlock();}
	
	private void lockForWrite() {readWriteLock.writeLock().lock();}
	private void unlockAfterWrite(){readWriteLock.writeLock().unlock();}

	@Override
	public String getServiceName() {return WebSSOService.SERVICE_NAME;}
	
}